﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;

namespace iTool.ClusterComponent
{
    public class LimitingService : iToolServiceBase, ILimitingService
    {
        private ConcurrentQueue<DateTime> runQueue = new ConcurrentQueue<DateTime>();
        private ConcurrentQueue<TaskCompletionSource<Task>> waitQueue = new ConcurrentQueue<TaskCompletionSource<Task>>();
        private TimeSpan timeoutDate { get; set; } = TimeSpan.FromSeconds(10);
        private long limiting{ get; set; }


        public override Task OnActivateAsync()
        {
            this.limiting = this.GetLongKey(out _);
            base.RegisterTimer(x => this.CheckTimeoutOwner(), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
            return base.OnActivateAsync();
        }

        public async Task ApplyExcuterAsync()
        {
            if (this.runQueue.Count >= this.limiting)
            {
                var completionSource = new TaskCompletionSource<Task>();
                this.waitQueue.Enqueue(completionSource);
                await completionSource.Task;
            }
            else
            {
                this.runQueue.Enqueue(DateTime.Now);
            }
        }

        public async Task ApplyExcuterAsync(long inputLimiting)
        {
            this.limiting = inputLimiting > 0 ? inputLimiting : this.limiting;
            await this.ApplyExcuterAsync();
        }

        public async Task FreedOneAsync()
        {
            if (this.waitQueue.TryDequeue(out TaskCompletionSource<Task> source))
            {
                this.runQueue.TryDequeue(out _);
                this.runQueue.Enqueue(DateTime.Now);
                source.TrySetResult(Task.CompletedTask);
            }
            else
            {
                this.runQueue.TryDequeue(out _);
            }

            await Task.CompletedTask;
        }

        private async Task CheckTimeoutOwner() 
        {
            var timeouts = this.runQueue.Where(item => (DateTime.Now -item) > this.timeoutDate);
            foreach (var timeout in timeouts) 
            {
                if (this.waitQueue.TryDequeue(out TaskCompletionSource<Task> source))
                {
                    this.runQueue.TryDequeue(out _);
                    this.runQueue.Enqueue(DateTime.Now);
                    source.TrySetResult(Task.CompletedTask);
                }
                else
                {
                    this.runQueue.TryDequeue(out _);
                }
            }
            await Task.CompletedTask;
        }
    }
}
